/*
 * EvenEvenMachineStrategy.java
 *
 * Stateroom - Java framework library for finite state machines
 * Copyright (C) 2009 by Matthew Werny
 * All Rights Reserved
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
package net.iceflow.stateroom.examples.fa;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.Queue;

import net.iceflow.stateroom.Context;
import net.iceflow.stateroom.conditions.InputProviderCondition;
import net.iceflow.stateroom.exception.DuplicateStateException;
import net.iceflow.stateroom.exception.UnknownStateException;
import net.iceflow.stateroom.inputprovider.QueueInputProvider;
import net.iceflow.stateroom.interfaces.InputProvider;
import net.iceflow.stateroom.interfaces.MachineDefinitionControls;
import net.iceflow.stateroom.interfaces.MachineStrategy;
import net.iceflow.stateroom.models.InputProviderModel;
import net.iceflow.stateroom.SimpleState;

/** Defines an even-even finite automata state machine. */
public class EvenEvenMachineStrategy implements MachineStrategy<InputProviderModel<String>> {

   /** State indicating that an even number of "a" characters and an even number of "b" characters have been processed. */
   public static final SimpleState<InputProviderModel<String>> EVEN_EVEN = new SimpleState<InputProviderModel<String>>("EvenEven");

   /** State indicating that an even number of "a" characters and an odd number of "b" characters have been processed. */
   public static final SimpleState<InputProviderModel<String>> EVEN_ODD = new SimpleState<InputProviderModel<String>>("EvenOdd");

   /** State indicating that an odd number of "a" characters and an odd number of "b" characters have been processed. */
   public static final SimpleState<InputProviderModel<String>> ODD_ODD = new SimpleState<InputProviderModel<String>>("OddOdd");

   /** State indicating that an odd number of "a" characters and an even number of "b" characters have been processed. */
   public static final SimpleState<InputProviderModel<String>> ODD_EVEN = new SimpleState<InputProviderModel<String>>("OddEven");

   /** InputProviderCondition that evaluates as true, if the next input is an 'a'. */
   protected static final InputProviderCondition<String> INPUT_PROVIDER_CONDITION_A = new InputProviderCondition<String>("a");

   /** InputProviderCondition that evaluates as true, if the next input is an 'b'. */
   protected static final InputProviderCondition<String> INPUT_PROVIDER_CONDITION_B = new InputProviderCondition<String>("b");

   /**
    * Causes to the definition strategy to define the machine using the provided machine controls.
    * @param controls Controls with which to add states and transitions
    * @throws UnknownStateException If an unknown state is referenced
    * @throws DuplicateStateException If an attempt is made to add a duplicate state
    */
   @Override
   public void defineMachine(MachineDefinitionControls<InputProviderModel<String>> controls) throws UnknownStateException, DuplicateStateException {
      // Add the machine states
      controls.addState(EVEN_EVEN, true);
      controls.addState(EVEN_ODD, false);
      controls.addState(ODD_ODD, false);
      controls.addState(ODD_EVEN, false);

      // Add transition edges leaving even-even state
      controls.addTransition(EVEN_EVEN, EVEN_ODD, INPUT_PROVIDER_CONDITION_B);
      controls.addTransition(EVEN_EVEN, ODD_EVEN, INPUT_PROVIDER_CONDITION_A);

      // Add transition edges leaving even-odd state
      controls.addTransition(EVEN_ODD, EVEN_EVEN, INPUT_PROVIDER_CONDITION_B);
      controls.addTransition(EVEN_ODD, ODD_ODD, INPUT_PROVIDER_CONDITION_A);

      // Add transition edges leaving odd-odd state
      controls.addTransition(ODD_ODD, ODD_EVEN, INPUT_PROVIDER_CONDITION_B);
      controls.addTransition(ODD_ODD, EVEN_ODD, INPUT_PROVIDER_CONDITION_A);

      // Add transition edges leaving odd-even state
      controls.addTransition(ODD_EVEN, ODD_ODD, INPUT_PROVIDER_CONDITION_B);
      controls.addTransition(ODD_EVEN, EVEN_EVEN, INPUT_PROVIDER_CONDITION_A);
   }

   /**
    * Initializes the start state in the context.
    * <p>
    * Sets the current context to EVEN_EVEN.
    * </p>
    * @param context Context to populate
    */
   @Override
   public void populateStartState(Context<InputProviderModel<String>> context) {
      // Set the start state to be Even Even (0 a's and 0 b's to start)
      context.setCurrentState(EVEN_EVEN);
   }

   /**
    * Populates the context with a model.
    * <p>
    * Populates the context with it's own instance of an input provider model and a queue input provider.
    * </p>
    * @param context Context to populate
    */
   @Override
   public void populateContext(Context<InputProviderModel<String>> context) {
      // Create a new queue to act as the internal implementation of the Input provider
      Queue<String> queue = new LinkedBlockingQueue<String>();

      // Create a new InputProvider adapter for the queue
      InputProvider<String> provider = new QueueInputProvider<String>(queue);

      // Create a new model adapter that allows data to be sourced from the InputProvider
      InputProviderModel<String> model = new InputProviderModel<String>(provider);

      // Insert the model into the context
      context.setModel(model);
   }
}
